#include "FastFile.h"
#include <algorithm>
#include <stdexcept>
#include <cstdio>

FastOFile::FastOFile(const std::string& filename, int bufferSize) 
: filename_(filename), 
  buffer_(0),
  bufferSize_(std::min(bufferSize, 1024)),
  buffOffset_(0),
  file_(0)
{
}

FastOFile::~FastOFile() {
    close();
    delete [] static_cast<char*>(buffer_);
}

void FastOFile::open() {
    close();
    file_ = ::fopen(filename_.c_str(), "wb");
    if (!file_) {
        throw std::runtime_error("failed to open binary file " + filename_ + " for writing");
    }
    
    if (!buffer_) {
        buffer_ = new char[bufferSize_];
    }
}

void FastOFile::close() {
    if (file_) {
        flush();
        ::fclose(file_);
        file_ = 0;
    }
}

void FastOFile::flush() {
    if (!file_ || buffOffset_ <= 0) {
        return;
    }

    if (buffOffset_ != ::fwrite(buffer_, 1, buffOffset_, file_)) {
        throw std::runtime_error("failed to write to " + filename_);
    }
    
    buffOffset_ = 0;
}

void FastOFile::write(const void* data, int bytesToWrite) {
    if (!file_) {
        open();
    }

    while (bytesToWrite > 0) {
        const int bytes = std::min(bytesToWrite, bufferSpace());
        appendToBuffer(data, bytes);
        bytesToWrite -= bytes;
        data = static_cast<const char*>(data) + bytes;
        if (!bufferSpace()) {
            flush();
        }
    }
}

int FastOFile::bufferSpace() const {
    return bufferSize_ - buffOffset_;
}

void* FastOFile::curBufferPos() const {
    return static_cast<char*>(buffer_) + buffOffset_;
}

void FastOFile::appendToBuffer(const void* data, int length) {
    if (length > bufferSpace()) {
        throw std::runtime_error("FastOFile buffer overflow");
    }

    ::memcpy(curBufferPos(), data, length);
    buffOffset_ += length;
}


/////////////////////////////////////////////////////////////


FastIFile::FastIFile(const std::string& filename, int bufferSize)
: filename_(filename), 
  buffer_(0),
  headPos_(0),
  bufferSize_(std::min(bufferSize, 1024)),
  bufferUsed_(0),
  file_(0),
  remainFileSize_(0)
{
}

FastIFile::~FastIFile() {
    close();
    delete [] static_cast<char*>(buffer_);
}

void FastIFile::open() {
    close();
    file_ = ::fopen(filename_.c_str(), "rb");
    if (!file_) {
        throw std::runtime_error("failed to open binary file " + filename_ + " for reading");
    }
    
    ::fseek(file_, 0, SEEK_END);
    remainFileSize_ = ::ftell(file_);
    ::fseek(file_, 0, SEEK_SET);
    //::rewind(file_);
    
    if (!buffer_) {
        buffer_ = new char [bufferSize_];
    }
    
    headPos_ = buffer_;
}

void FastIFile::close() {
    if (file_) {
        ::fclose(file_);
        file_ = 0;
    }
}

int FastIFile::read(void* data, int bytesToRead) {
    if (!file_) {
        open();
    }

    int bytesRead = 0;
    while (bytesRead < bytesToRead) {
        if (unreadBufferLen() <= 0) {
            readToBuffer();
        }
        const int bytes = std::min(unreadBufferLen(), bytesToRead);
        if (bytes <= 0) {
            return bytesRead;
        }
        readFromBuffer(data, bytes);
        bytesRead += bytes;
        data = static_cast<char*>(data) + bytes;
    }
    
    return bytesRead;
}

void FastIFile::readToBuffer() {
    if (remainFileSize_ <= 0) {
        return;
    }
    
    const int bytes = std::min(remainFileSize_, bufferSize_);
    
    if (bytes != ::fread(buffer_, 1, bytes, file_)) {
        throw std::runtime_error("failed to read from file " + filename_);
    }
    
    bufferUsed_ = bytes;
    remainFileSize_ -= bytes;
    headPos_ = buffer_;
}

void FastIFile::readFromBuffer(void* data, int bytesToRead) {
    if (bytesToRead > unreadBufferLen()) {
        throw std::runtime_error("FastIFile read buffer overflow");
    }
    ::memcpy(data, headPos_, bytesToRead);
    headPos_ = static_cast<char*>(headPos_) + bytesToRead;
}

int FastIFile::unreadBufferLen() const {
    return bufferUsed_ - (static_cast<char*>(headPos_) - static_cast<char*>(buffer_));
}
